create temp CG for consistent VM snapshot for the VM which is span across multiple flexvolumes#74
create temp CG for consistent VM snapshot for the VM which is span across multiple flexvolumes#74rajiv-jain-netapp wants to merge 12 commits into
Conversation
… multiple volumes at storage
… at multiple flexvolumes
…ective checks by which delete snapshot would be handled from primary only
|
testing results and all the executed test cases added under previous PR #67 |
sandeeplocharla
left a comment
There was a problem hiding this comment.
Partially reviewed. Still need to go through a good portion of OntapVMSnapshotStrategy.java and SnapshotManagerImpl.java
There was a problem hiding this comment.
Pull request overview
This PR enhances NetApp ONTAP managed-storage snapshot handling by ensuring VM snapshots remain consistent when a VM’s volumes span multiple FlexVols, and by preventing managed PRIMARY (array-native) volume snapshots from being incorrectly archived/bookkept as secondary snapshots.
Changes:
- Add a temporary ONTAP Consistency Group (CG) two-phase snapshot flow for VM snapshots spanning multiple FlexVols; keep a direct FlexVol snapshot fast-path for single-FlexVol VMs.
- Ensure managed PRIMARY volume snapshots (e.g., ONTAP volume snapshots) remain on primary/array storage and do not use secondary-archive bookkeeping paths.
- Extend ONTAP integration utilities/strategy to better separate “provisioning-time” aggregate validation from “operations-only” connectivity, and add CG REST client APIs + related unit tests.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java | Avoids archiving managed PRIMARY snapshots to secondary; refactors decision into archiveSnapshotToSecondary. |
| plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/vmsnapshot/OntapVMSnapshotStrategy.java | Implements temporary CG orchestration for multi-FlexVol VM snapshots, plus single-FlexVol direct snapshot path and improved naming. |
| plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java | Adds operations-only connect mode, job polling helpers, and centralized FlexVol snapshot delete orchestration. |
| plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/OntapStorageUtils.java | Adds snapshot naming helpers and an overload to connect with/without aggregate validation. |
| plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/OntapStorageConstants.java | Adds SVM UUID support and CG/SFSR/delete polling constants. |
| plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java | Persists resolved SVM UUID into pool details when available. |
| plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SnapshotFeignClient.java | Adds ONTAP CG REST API endpoints (create/list CG, start/commit snapshot, delete CG). |
| plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java | Delegates volume-snapshot delete/revert to strategy helpers; updates naming and makes delete more robust/idempotent. |
| plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/vmsnapshot/OntapVMSnapshotStrategyTest.java | Adds/updates unit tests for CG flow, naming, scope resolution, and avoids static utility connection issues. |
| plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/service/StorageStrategyTest.java | Adds tests for operations-only connect, job polling helpers, and FlexVol snapshot delete orchestration. |
| plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriverTest.java | Updates capabilities expectations to include revert support. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return null; | ||
| } | ||
|
|
||
| private String getStringField(Map<String, Object> record, String key) { |
There was a problem hiding this comment.
This can be in utils, instead.
| @RequestLine("POST /api/application/consistency-groups") | ||
| @Headers({"Authorization: {authHeader}", "Content-Type: application/json"}) | ||
| JobResponse createConsistencyGroup(@Param("authHeader") String authHeader, | ||
| Map<String, Object> request); |
There was a problem hiding this comment.
Isn't it better to create a model for CG request, instead?
|
|
||
| logger.info("takeVMSnapshot: [CG:Start] Starting phase-1 snapshot [{}] for temporary consistency group [{}]", | ||
| snapshotNameBase, tempCgUuid); | ||
| cgSnapshotUuid = resolveStartedConsistencyGroupSnapshotUuid(snapshotClient, storageStrategy, |
There was a problem hiding this comment.
We can have null check for cgSnapshotUuid so that null is not passed to commitConsistencyGroupSnapshot for actual API call.
| * Validates ONTAP cluster reachability, credentials, SVM state, protocol, and aggregate capacity | ||
| * for new FlexVol creation (primary pool provisioning). | ||
| */ | ||
| public boolean connect() { |
There was a problem hiding this comment.
if this method is always calling public boolean connect(boolean validateAggregatesForVolumeCreation) , what is this method intended for ? We can keep one of them or have default value for validateAggregatesForVolumeCreation if caller does not want to pass.
| if (eligibleAggregates.isEmpty()) { | ||
| logger.error("No suitable aggregates found on SVM " + svmName + " for volume operations."); | ||
| throw new CloudRuntimeException("No suitable aggregates found on SVM " + svmName + " for volume operations."); | ||
| this.resolvedSvmUuid = svm.getUuid(); |
There was a problem hiding this comment.
Since we are fetching svm uuid now , lets explicitly add uuid in queryParams
| @@ -685,13 +715,116 @@ public Boolean jobPollForSuccess(String jobUUID, int maxRetries, int sleepTimeIn | |||
| Thread.sleep(sleepTimeInMilliSecs); | |||
| } | |||
| if (jobResp == null || !jobResp.getState().equals(OntapStorageConstants.JOB_SUCCESS)) { | |||
There was a problem hiding this comment.
This condition will always be false now as while will take care of these conditions.
| return; | ||
| } | ||
| Boolean success = jobPollForSuccess(response.getJob().getUuid(), maxRetries, pollIntervalMs); | ||
| if (!Boolean.TRUE.equals(success)) { |
There was a problem hiding this comment.
jobPollUntilSuccess always either throws CloudRuntimeException or returns a non-null Job . It never returns null. So jobPollForSuccess always returns true or throws. So this is in a way dead-code
| * {@code snapshot_data_store} row must remain so volume-snapshot DELETE uses | ||
| * {@code StorageSystemSnapshotStrategy} and the primary datastore driver.</p> | ||
| */ | ||
| private boolean isManagedPrimaryLocationSnapshot(StoragePool storagePool, CreateSnapshotPayload payload) { |
There was a problem hiding this comment.
Is this method limited to ONTAP snapshots? if yes, we should add our provider check as this will impact all the managed storage pool.
| String buildTemporaryConsistencyGroupName(VMSnapshot vmSnapshot) { | ||
| return OntapStorageUtils.buildOntapSnapshotName( | ||
| OntapStorageConstants.ONTAP_TEMP_CG_PREFIX + vmSnapshot.getId(), | ||
| "cg" + vmSnapshot.getId()); |
There was a problem hiding this comment.
vmSnapshot.getId will appear twice in temp cg name ?
| StorageStrategy storageStrategy = OntapStorageUtils.getStrategyByStoragePoolDetails(poolDetails); | ||
| SnapshotFeignClient client = storageStrategy.getSnapshotFeignClient(); | ||
| String authHeader = storageStrategy.getAuthHeader(); | ||
| StorageStrategy storageStrategy = resolveStorageStrategy(poolDetails); |
There was a problem hiding this comment.
This and deleteFlexVolSnapshotForCloudStackVolume can throw exception which will cause line vmSnapshotDetailsDao.remove(detailVO.getId()); to skip. That will cause snapshot to be in still present in DB. May be we can move vmSnapshotDetailsDao.remove(detailVO.getId()); in finally block with these 2 on try
| if (lunDetail.getValue() == null) { | ||
| throw new CloudRuntimeException("LUN UUID not found for iSCSI volume " + volumeVO.getId()); | ||
| } | ||
| lunUuid = lunDetail.getValue(); |
There was a problem hiding this comment.
no need of this extra lunUuid variable assignment with temp lunUUID var, can be done directly also
| name = StringUtils.left(volumeName, volumeName.length() - trimRequired) + "-" + snapshotUuid; | ||
| } | ||
| return name; | ||
| private String buildSnapshotName(String cloudStackSnapshotName, long snapshotId) { |
There was a problem hiding this comment.
as discussed, we have to check for two cloudstack using same ontap.
| } | ||
| return name; | ||
| private String buildSnapshotName(String cloudStackSnapshotName, long snapshotId) { | ||
| return OntapStorageUtils.buildOntapSnapshotName(cloudStackSnapshotName, "cs" + snapshotId); |
There was a problem hiding this comment.
we can move this cs to constant
Also, for better readability we can use - (hyphen)
Please add example for snapshot name
| @RequestLine("POST /api/application/consistency-groups") | ||
| @Headers({"Authorization: {authHeader}", "Content-Type: application/json"}) | ||
| JobResponse createConsistencyGroup(@Param("authHeader") String authHeader, | ||
| Map<String, Object> request); |
There was a problem hiding this comment.
to create CG, request should be model type why map?
| */ | ||
| @RequestLine("GET /api/application/consistency-groups") | ||
| @Headers({"Authorization: {authHeader}"}) | ||
| OntapResponse<Map<String, Object>> getConsistencyGroups(@Param("authHeader") String authHeader, |
There was a problem hiding this comment.
the return type should be OntapResponse of type ConsistencyGroup Object
| */ | ||
| @RequestLine("POST /api/application/consistency-groups/{cgUuid}/snapshots") | ||
| @Headers({"Authorization: {authHeader}", "Content-Type: application/json"}) | ||
| JobResponse createConsistencyGroupSnapshot(@Param("authHeader") String authHeader, |
| */ | ||
| @RequestLine("GET /api/application/consistency-groups/{cgUuid}/snapshots") | ||
| @Headers({"Authorization: {authHeader}"}) | ||
| OntapResponse<Map<String, Object>> getConsistencyGroupSnapshots(@Param("authHeader") String authHeader, |
| groupInfo.volumeIds); | ||
| // CG orchestration is only required when VM disks span multiple FlexVols. | ||
| // A single FlexVol already provides atomic capture for all volumes on that FlexVol. | ||
| if (flexVolGroups.size() > 1) { |
There was a problem hiding this comment.
can we also test, VM span across mutiple storage pool where svms are also diff
| if (ProtocolType.ISCSI.name().equalsIgnoreCase(protocol)) { | ||
| VolumeDetailVO lunDetail = volumeDetailsDao.findDetail(volumeVO.getId(), OntapStorageConstants.LUN_DOT_UUID); | ||
| String lunUUID = lunDetail != null ? lunDetail.getValue() : null; | ||
| if (lunUUID == null) { | ||
| if (lunDetail.getValue() == null) { | ||
| throw new CloudRuntimeException("LUN UUID not found for iSCSI volume " + volumeVO.getId()); | ||
| } | ||
| lunUuid = lunDetail.getValue(); |
| Aggregate aggrResp = aggregateFeignClient.getAggregateByUUID(authHeader, aggr.getUuid()); | ||
| if (aggrResp == null) { | ||
| logger.warn("Aggregate details response is null for aggregate " + aggr.getName() + ". Skipping."); | ||
| break; | ||
| } |
| /** | ||
| * Single GET attempt: try to match by name, | ||
| * then fall back to listing all CG snapshots in this group (And it would be one | ||
| * always since workflow is keep deleting the CG). | ||
| */ |
Description
This PR...
Types of changes
Feature/Enhancement Scale or Bug Severity
Feature/Enhancement Scale
Bug Severity
Screenshots (if appropriate):
How Has This Been Tested?
How did you try to break this feature and the system with this change?